Titanic: Machine Learning from Disaster adalah sebuah kompetisi di Kaggle yang biasanya diikuti setelah menyelesaikan khursus Machine Learning di website tersebut, tetapi kompetisi ini juga terbuka bagi siapa pun yang ingin berkontribusi.
Goal : Dengan menggunakan data penumpang Titanic (name, age, price of ticket, etc) mencoba untuk memprediksi siapa yang akan selamat dan yang akan tidak selamat pada data test.csv. Dalam file test.csv tidak di sertakan kolom Survived, itu adalah tugas kita untuk memprediksinya.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
train_data = pd.read_csv('titanic/train.csv')
train_data.tail()
test_data = pd.read_csv('titanic/test.csv')
test_data.head()
submission = pd.read_csv('titanic/gender_submission.csv')
submission.head()
def concat_df(train_data, test_data):
return pd.concat([train_data, test_data], sort=True).reset_index(drop=True)
df_all = concat_df(train_data, test_data)
train_data.name = 'Training Set'
test_data.name = 'Test Set'
df_all.name = 'All Set'
df_all.head()
Penjelasan dari dataset
submission_data = pd.read_csv('titanic/gender_submission.csv')
submission_data.head()
Data submission adalah contoh data hasil prediksi yang akan diupload ke Kaggle
Untuk mengetahui bentuk dari dataframe kita, berapa banyak row dan column yang ada di dalamnya
print(f'Training X Shape = {train_data.shape}')
print(f'Training y Shape = {train_data.Survived.shape}')
print(f'Test X Shape = {test_data.shape}')
print('\n')
print(train_data.columns)
print(test_data.columns)
df_all.describe()
df_all.info()
Kita dapat melihat bahwa ada beberapa data yang kosong di kolom 'Embarked, 'Age', dan 'Cabin'
Kita akan melihat jumlah data yang kosong tersebut
print('TRAIN DATA \n',train_data.isnull().sum(),'\n')
print('TEST DATA \n',test_data.isnull().sum())
df_all.isnull().sum()
df_all_cor = df_all.corr().abs()
df_all_cor
Korelasi dengan mendekati nilai 1 untuk korelasi positive dan nilai -1 untuk korelasi terbalik.
Pada data ini dapat dilihat bahwa target variable kita Survived sangat besar korelasinya dengan Pclass dan Fare.
Sedangkan Age sangat berkaitan dengan Pclass, Sibling Spouse (SibSp), Parent Children (Parch).
Dapat diasumsikan bahwa kebanyakan orang yang selamat adalah orang dengan PClass atas dan Tuanya umur seseorang dapat dikatakan dia akan membawa saudara/orang tua/anak/pasangan.
Dan Fare (harga) berkaitan dengan Pclass (kelas penumpang) seorang penumpang.
Age => Untuk mengisi data Age yang kosong, karena kita mengetahui bahwa Umur seseorang berkaitan dengan Kelas penumpang, kita dapat mengisinya dari nilai tengah umur seseorang di dalam kelas tersebut dan berdasar jenis kelamin. Jadi saya akan mengisi berdasarkan hal tersebut
age_by_pclass_sex = df_all.groupby(['Sex', 'Pclass']).median()['Age']
print(age_by_pclass_sex, '\n')
for pclass in range(1,4):
for sex in ['female', 'male']:
print(f'Median age of pclass {pclass} {sex}s : {age_by_pclass_sex[sex][pclass]}')
# Filling the missing values in Age with the medians of Sex and Pclass groups
df_all['Age'] = df_all.groupby(['Sex', 'Pclass'])['Age'].apply(lambda x: x.fillna(x.median()))
Embarked => Untuk Embarked, kebanyakan orang dari Titanic berangkat dari Southampton / S.
fig, axs = plt.subplots(ncols=1, figsize=(10,10))
sns.countplot(x='Embarked', hue='Survived', data=train_data)
plt.legend(['Not Survived', 'Survived'])
plt.title('Count of Survival Embarked')
plt.xlabel('Embarked')
plt.ylabel('Passenger Count')
plt.show()
Sehingga kita dapat mengisinya dengan S saja. Ini lebih baik daripada membiarkan datanya kosong.
df_all['Embarked'] = df_all['Embarked'].fillna('S')
Fare => Untuk Fare, kita tahu bahwa Fare sangat berkaitan dengan Kelas Penumpang, Jumlah Parent/Children, dan Jumlah Sibling/Spouse. Sehingga kita dapat mengisinya dengan nilai tengah dari orang yang berada di grup tersebut.
med_fare = df_all.groupby(['Pclass', 'Parch', 'SibSp']).Fare.median()[3][0][0]
# Filling the missing value in Fare with the median Fare of 3rd class alone passenger
df_all['Fare'] = df_all['Fare'].fillna(med_fare)
Ingat tujuan : kita ingin menemukan pola di dataset train_data.csv yang membantu untuk meprediksi penumpang di test_data.csv selamat dan tidak selamat. .
File sampel yang dikirim dalam gender_submission.csv mengasumsikan bahwa semua penumpang wanita selamat (dan semua penumpang pria meninggal).
Apakah ini dugaan yang masuk akal? Kita akan memeriksa apakah pola ini juga berlaku di dalam data train_data.csv.
women = train_data.loc[train_data.Sex == 'female']["Survived"]
rate_women = sum(women)/len(women)
print(f'{rate_women} wanita yang selamat')
men = train_data.loc[train_data.Sex == 'male']['Survived']
rate_men = sum(men)/len(men)
print(f'{rate_men} pria yang selamat')
Hasil di atas adalah persentase penumpang wanita dan pria (didalam train_data.csv) yang selamat.
Dari sini dapat dilihat bahwa :
fig, axs = plt.subplots(ncols=1, figsize=(10,5))
sns.countplot(x='Sex', hue='Survived', data=train_data)
plt.legend(['Not Survived', 'Survived'])
plt.title('Count of Survival Embarked')
plt.xlabel('Embarked')
plt.ylabel('Passenger Count')
plt.show()
Gender tampaknya merupakan indikator kuat untuk bertahan hidup dikapal tersebut
File gender_submission.csv mendasarkan prediksi pada hanya satu kolom.
Dengan mempertimbangkan banyak kolom kita dapat menemukan pola yang lebih kompleks yang berpotensi menghasilkan prediksi yang lebih baik.
train_data_no_na = df_all.loc[0:890]
cat_features = ['Embarked', 'Parch', 'Pclass', 'Sex', 'SibSp']
fig, axes = plt.subplots(ncols=2, nrows=2, figsize=(10,10))
plt.subplots_adjust(right=2.5, top=2)
for i, feature in enumerate(cat_features, 1):
plt.subplot(2,3,i)
sns.countplot(x=feature, hue='Survived', data=train_data_no_na)
plt.xlabel(f'{feature}', size=20, labelpad=15)
plt.ylabel('Passenger Count', size=20, labelpad=15)
plt.tick_params(axis='x', labelsize=20)
plt.tick_params(axis='y', labelsize=20)
plt.legend(['Not Survived', 'Survived'], prop={'size':18})
plt.title(f'Count of Survival in {feature} Feature', size=25, y=1.01)
plt.show()
Embarked = Dari plot yang dihasikan, penumpang yang berangkat dari Cherbourg lebih banyak yang selamat, sedangkan penumpang dari Southampton, hanya setengahnya yang selamat.
Parch = Untuk orang yang membawa hanya 1 Parent/Children, lebih banyak yang selamat.
PClass = Untuk orang dengan Kelas Penumpang 1, kemungkinan selamatnya jauh lebih tinggi. Dan penumpang dengan Kelas Penumpang 3 hanya sedikit yang selamat.
Sex = Dan Orang yang membawa 1 Sibling / Spouse kemungkinan selamatnya jauh lebih besar.
SibSp = Orang yang membawa 2 Sibling / Spouse kemungkinan selamatnya cukup kecil.
Untuk Data Fare dan Age, akan dibagi ke dalam beberapa Bin / Group.
Fare = Dan untuk Fare termurah adalah 0 dan termahal adalah 512.
print(f'Fare terendah = {df_all.Fare.min()}')
print(f'Fare tertinggi = {df_all.Fare.max()}')
Sehingga dapat dibagi menjadi 13 kelompok quantile mungkin cukup.
train_data_no_na['Fare'] = pd.qcut(train_data_no_na['Fare'], 13)
train_data_no_na.loc[:, ['Fare']]
Berikut visualisasi kolom Fare
fig, axis = plt.subplots(figsize=(22,15))
sns.countplot(y='Fare', hue='Survived', data=train_data_no_na)
plt.xlabel('Passenger Count', size=20, labelpad=20)
plt.ylabel('Fare', size=20, labelpad=20)
plt.tick_params(axis='x', labelsize=20)
plt.tick_params(axis='y', labelsize=20)
plt.legend(['Not Survived', 'Survived'], loc='upper right', prop={'size': 15})
plt.title('Count of Survival in {} Feature'.format('Fare'), size=25, y=1.01)
plt.show()
Dapat dilihat bahwa semakin mahal harga tiketnya, semakin besar kemungkinan orang itu selamat, dimulai dari harga 56 ke atas.
Tetapi banyak orang yang selamat dimulai dari 10.5 sudah cukup meningkat, kecuali kejadian yang terjadi pada kelompok pemegang tiket berharga 13-15.85.
Age = Dari fungsi describe sebelumnya, kita dapat melihat usia termuda adalah 0.17 tahun dan yang tertua adalah 80 tahun. Kode dibawah akan menunjukkan nilai terendah dan tertinggi dari kolom Age.
Sehingga membaginya dengan 10 kelompok quantile mungkin cukup.
df_all['Age'] = pd.qcut(df_all['Age'], 10)
df_all.Age.min()
train_data_no_na = df_all.loc[:890]
fig, axis = plt.subplots(figsize=(22,15))
sns.countplot(y='Age', hue='Survived', data=train_data_no_na)
plt.xlabel('Passenger Count', size=20, labelpad=20)
plt.ylabel('Age', size=20, labelpad=20)
plt.tick_params(axis='x', labelsize=20)
plt.tick_params(axis='y', labelsize=20)
plt.legend(['Not Survived', 'Survived'], loc='upper right', prop={'size': 15})
plt.title('Count of Survival in {} Feature'.format('Age'), size=25, y=1.01)
plt.show()
Plot menggambarkan group usia terhadap tingkat keselamatan. Dapat kita lihat, rentang umur 0 sampai 14 tahun memiliki tingkat keselamatan paling tinggi dan rentang umur 31-36 di urutan kedua.
Tingkat kematian paling tinggi ada di rentang umur 22 - 25
Fitur pertama yang akan dibuat adalah Family_Size, sesuai namanya ini adalah gabungan dari: Parent, Children, Sibling, dan Spouse. Lalu kita tambahkan 1 dengan asumsi menghitung diri orang itu juga.
Fitur kedua yang kita buat adalah menggabungkan Family_Size dengan group nya masing” tergantung jumlahnya.
Kategorinya adalah seperti berikut.
Family Size 7, 8 dan 11 = Large
df_all['Family_Size_Grouped'] = df_all['Family_Size'].map(family_map) Setelah itu kita gambarkan plotnya.
df_all['Family_Size'] = df_all['SibSp'] + df_all['Parch'] + 1
fig, axs = plt.subplots(figsize=(20,20), ncols=2, nrows=2)
plt.subplots_adjust(right=1.5)
sns.barplot(x=df_all['Family_Size'].value_counts().index,
y=df_all['Family_Size'].value_counts().values, ax=axs[0][0])
sns.countplot(x='Family_Size', hue='Survived', data=df_all, ax=axs[0][1])
axs[0][0].set_title('Family Size Feature Value Counts', size=25, y=1.05)
axs[0][1].set_title('Survival Counts In Family Size', size=25, y=1.05)
family_map = {1:'Alone', 2:'Small', 3:'Small', 4:'Small', 5:'Medium',
6:'Medium', 7:'Large', 8:'Large', 11:'Large'}
df_all['Family_Size_Grouped'] = df_all['Family_Size'].map(family_map)
sns.barplot(x=df_all['Family_Size_Grouped'].value_counts().index,
y=df_all['Family_Size_Grouped'].value_counts().values, ax=axs[1][0])
sns.countplot(x='Family_Size_Grouped', hue='Survived', data=df_all, ax=axs[1][1])
axs[1][0].set_title('Family Size Feature Value Counts After Grouping', size=25, y=1.05)
axs[1][1].set_title('Survival Counts in Family Size After Grouping', size=25, y=1.05)
for i in range(2):
axs[i][1].legend(['Not Survived', 'Survived'], loc='upper right', prop={'size':20})
for j in range(2):
axs[i][j].tick_params(axis='x', labelsize=20)
axs[i][j].tick_params(axis='y', labelsize=20)
axs[i][j].set_xlabel('')
axs[i][j].set_ylabel('')
plt.show()
Pada plot menjelaskan bahwa orang dengan Family_Size 2,3,4 mempunyai kemungkinan selamat lebih besar, selebihnya kemungkinannya menurun dan orang yang pergi dengan Family_Size_Grouped Small lebih terlihat mempunyai tingkat keselamatan lebih besar.
Feature baru bernama Ticket_Frequency dengan nilai gabungan dari Ticket yang sama.
df_all.loc[:,['Ticket']]
df_all['Ticket_Frequency'] = df_all.groupby('Ticket')['Ticket'].transform('count')
fig, axs = plt.subplots(figsize=(22,9))
sns.countplot(x='Ticket_Frequency', hue='Survived', data=df_all)
plt.xlabel('Ticket Frequency', size=15, labelpad=20)
plt.ylabel('Passenger Count', size=15, labelpad=20)
plt.tick_params(axis='x', labelsize=15)
plt.tick_params(axis='y', labelsize=15)
plt.legend(['Not Survived', 'Survived'], loc='upper right', prop={'size': 15})
plt.title('Count of Survival in {} Feature'.format('Ticket Frequency'), size=15, y=1.05)
plt.show()
Pada hasil plot feature Family_Size_Grouped, di angka 2,3,4 kemungkinan tingkat selamatnya meningkat.
Ada kemungkinan pembantu atau teman yang menggunakan tiket yang sama tetapi tidak terhitung sebagai keluarga sehingga feature Family_Size_Grouped ini bisa menambah akurasi model yang akan dibuat nanti.
Membuat fitur baru dengan nama ‘Title’ yang berisi jabatan seseorang.
df_all['Title'] = df_all['Name'].str.split(', ', expand=True)[1].str.split('.', expand=True)[0]
print(df_all['Title'].unique())
Jabatan yang paling umum di sini adalah Mr, Mrs. , dan Miss
df_all.head(2)
fig, axs = plt.subplots(nrows=2, figsize=(20, 20))
sns.barplot(x=df_all['Title'].value_counts().index, y=df_all['Title'].value_counts().values, ax=axs[0])
axs[0].tick_params(axis='x', labelsize=10)
axs[1].tick_params(axis='x', labelsize=15)
for i in range(2):
axs[i].tick_params(axis='y', labelsize=15)
axs[0].set_title('Title Feature Value Counts', size=20, y=1.05)
# Bagian ini adalah bagian mengubah ['Miss', 'Mrs','Ms', 'Mlle', 'Lady', 'Mme', 'the Countess', 'Dona']
# akan kita ubah menjadi 'Miss/Mrs/Ms' ['Dr', 'Col', 'Major', 'Jonkheer', 'Capt', 'Sir', 'Don', 'Rev']
# menjadi 'Dr/Military/Noble/Clergy'
df_all['Title'] = df_all['Title'].replace(['Miss', 'Mrs','Ms', 'Mlle', 'Lady', 'Mme',
'the Countess', 'Dona'], 'Miss/Mrs/Ms')
df_all['Title'] = df_all['Title'].replace(['Dr', 'Col', 'Major', 'Jonkheer', 'Capt', 'Sir', 'Don',
'Rev'], 'Dr/Military/Noble/Clergy')
sns.barplot(x=df_all['Title'].value_counts().index, y=df_all['Title'].value_counts().values, ax=axs[1])
axs[1].set_title('Title Feature Value Counts After Grouping', size=20, y=1.05)
plt.show()
df_all.head(2)
from sklearn.preprocessing import OneHotEncoder, LabelEncoder, StandardScaler
train_data_no_na = df_all[0:890]
test_data_no_na = df_all[891:]
from sklearn.preprocessing import OneHotEncoder, LabelEncoder, StandardScaler
dfs=[train_data_no_na,test_data_no_na]
non_numeric_features = ['Embarked', 'Sex', 'Title', 'Family_Size_Grouped', 'Age', 'Fare']
for df in dfs:
for feature in non_numeric_features:
df[feature] = LabelEncoder().fit_transform(df[feature])
cat_features = ['Pclass', 'Sex', 'Embarked', 'Title', 'Family_Size_Grouped']
encoded_features = []
for df in dfs:
for feature in cat_features:
encoded_feat = OneHotEncoder().fit_transform(df[feature].values.reshape(-1, 1)).toarray()
n = df[feature].nunique()
cols = ['{}_{}'.format(feature, n) for n in range(1, n + 1)]
encoded_df = pd.DataFrame(encoded_feat, columns=cols)
encoded_df.index = df.index
encoded_features.append(encoded_df)
df_train = pd.concat([train_data_no_na, *encoded_features[:5]], axis=1)
df_test = pd.concat([test_data_no_na, *encoded_features[5:]], axis=1)
Berikut ini adalah dataset berisi parameter yang akan digunakan untuk membuat model machine learning
df_all = concat_df(df_train, df_test)
drop_cols = ['Cabin', 'Embarked', 'Survived', 'Name', 'PassengerId', 'Pclass', 'Sex', 'Ticket', 'Title','Family_Size',
'Family_Size_Grouped']
df_all.drop(columns=drop_cols, inplace=True)
df_all.head()
df_all.columns
Melakukan normalisasi data agar data yang digunakan tidak memiliki penyimpangan yang besar.
df_train.shape
X_train = StandardScaler().fit_transform(df_train.drop(columns=drop_cols))
y_train = df_train['Survived'].values
X_test = StandardScaler().fit_transform(df_test.drop(columns=drop_cols))
print('X_train shape: {}'.format(X_test.shape))
print('y_train shape: {}'.format(y_train.shape))
print('X_test shape: {}'.format(X_test.shape))
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from xgboost import XGBClassifier
from sklearn.metrics import classification_report
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_absolute_error
decision_tree = DecisionTreeClassifier(criterion='gini',
max_depth=5,
min_samples_split=4,
min_samples_leaf=5,
max_features='auto',
random_state=50)
decision_tree.fit(X_train, y_train)
decision_tree_pred = decision_tree.predict(X_test)
scores_dt = cross_val_score(decision_tree, X_train, y_train, cv=10, scoring = "accuracy")
print("Scores:", scores_dt)
print("Mean:", scores_dt.mean())
print("Standard Deviation:", scores_dt.std())
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
knn_pred = knn.predict(X_test)
scores_knn = cross_val_score(knn, X_train, y_train, cv=10, scoring = "accuracy")
print("Scores:", scores_knn)
print("Mean:", scores_knn.mean())
print("Standard Deviation:", scores_knn.std())
Model ini dibangun dari beberapa "tree" yang secara individual akan mempertimbangkan data setiap penumpang dan memilih apakah individu tersebut selamat. Kemudian, model ini membuat keputusan demokratis: hasil dengan suara terbanyak menang!
def get_mae(max_leaf_nodes, X_train,y_train):
my_model = RandomForestClassifier(max_leaf_nodes=max_leaf_nodes, random_state=0)
my_model.fit(X_train,y_train)
preds = my_model.predict(X_train)
mae = mean_absolute_error(y_train, preds)
return (mae)
for max_leaf_nodes in [5,50,100, 500,1000,1100]:
my_mae = get_mae(max_leaf_nodes, X_train, y_train)
print(f'Max leaf nodes {max_leaf_nodes} \t\t Mean Absolute Error {my_mae}')
rf_model = RandomForestClassifier(criterion='gini',
n_estimators=500,
max_depth=5,
min_samples_split=4,
min_samples_leaf=5,
max_features='auto',
oob_score=True,
random_state=50)
rf_model.fit(X_train,y_train)
rf_pred = rf_model.predict(X_test)
scores_rf = cross_val_score(rf_model, X_train, y_train, cv=10, scoring = "accuracy")
print("Scores:", scores_rf)
print("Mean:", scores_rf.mean())
print("Standard Deviation:", scores_rf.std())
xgb_model = XGBClassifier(n_estimators = 1000, learning_rate = 0.05)
xgb_model.fit(X_train,y_train)
xgb_pred = xgb_model.predict(X_test)
scores_xgb = cross_val_score(xgb_model, X_train, y_train, cv=10, scoring = "accuracy")
print("Scores:", scores_xgb)
print("Mean:", scores_xgb.mean())
print("Standard Deviation:", scores_xgb.std())
models = pd.DataFrame({
'Model': ['Decision Tree', 'KNN','Random Forest','XGBoosting'],
'Score': [(scores_dt.mean()*100).round(2).astype(str)+'%',
(scores_knn.mean()*100).round(2).astype(str)+'%',
(scores_rf.mean()*100).round(2).astype(str)+'%',
(scores_xgb.mean()*100).round(2).astype(str)+'%']
})
models.sort_values(by="Score", ascending=False).reset_index(drop=True)
output = pd.DataFrame({'PassengerId' : test_data_no_na.PassengerId, 'Survived':rf_pred})
output = output.astype(int)
output.head()
output.to_csv('my_submission.csv', index=False)
Dari semua machine learning model diatas, Random forest berada pada pringkat pertama dengan akurasi 83.26%. Maka dari itu saya menggunakan Random Forest untuk memprediksi keselamatan penumpang titanic dan mengupload hasil prediksi ke situs Kaggle.
Skor terakhir yang saya dapatkan adalah sebagai berikut:
from IPython.display import Image
Image('image2.png')
from IPython.display import Image
Image('image.png')